#include "config.h"
#include <getopt.h>
#include "llvmwrapper.hpp"
#include "parser.hpp"
#include <sstream>
#include <experimental/filesystem>

#include <llvm/InitializePasses.h>
#include <llvm/LinkAllPasses.h>
#include <llvm/Target/TargetOptions.h>
#include <llvm/Target/TargetMachine.h>
#include <llvm/Support/FileSystem.h>
#include <llvm/Support/TargetSelect.h>
#include <llvm/Support/TargetRegistry.h>
#include <llvm/Support/ToolOutputFile.h>
#include <llvm/Support/FormattedStream.h>
#include <llvm/Support/Host.h>
#include <llvm/Pass.h>
#include <llvm/Passes/PassBuilder.h>
#include <llvm/IR/PassManager.h>
#include <llvm/IR/Module.h>
#include <llvm/IR/LegacyPassManager.h>

namespace fs = std::experimental::filesystem::v1;

extern FILE *yyin;
StatementAST *program;

struct option opts[] = {
    {"version", 0, NULL, 'v'},
    {"outfile", 1, NULL, 'o'},
    {"compile", 0, NULL, 'c'},
    {"ir", 0, NULL, 0},
    {"help", 0, NULL, 'h'}};

void display_usage(void)
{
    puts( "doc2html - convert documents to HTML" );
}

// generate llvm IR
static void generateIR(StatementAST *ast, llvm::Module *module)
{
    ASTContext ctx;
    ctx.module = module;

    CodeBlockAST gloablblock;
    ctx.codeblock = &gloablblock;
    //ctx.astfunc = ;
    ((StatementAST *)(ast))->Codegen(ctx);
}

static int generateobj(std::shared_ptr<llvm::ToolOutputFile> out, llvm::Module* module)
{
    llvm::legacy::PassManager PM;
    llvm::TargetOptions  Options;

    std::string Err;

    llvm::Triple TheTriple(module->getTargetTriple());
    if (TheTriple.getTriple().empty())
        TheTriple.setTriple(llvm::sys::getDefaultTargetTriple());
    
    const llvm::Target* theTarget = llvm::TargetRegistry::lookupTarget(TheTriple.getTriple(), Err);

    std::string MCPU, FeaturesStr;

    llvm::TargetMachine * machine = theTarget->createTargetMachine(TheTriple.getTriple(), MCPU, FeaturesStr, Options, llvm::None);

    if (machine->addPassesToEmitFile(PM, out->os(), llvm::TargetMachine::CGFT_ObjectFile))
    {
        fprintf(stderr, "target does not support generation of this file type!\n" );
        return 1;
    }

    PM.run(*module);
    return 0;
}

int main(int argc, char const *argv[])
{
    std::string outfilename;
    const char *optstring = "vco:h";
    int c, index = 0;
    bool out_c = false;
    bool out_ir = false;

    while ((c = getopt_long(argc, (char *const *)argv, optstring, opts, &index)) != -1)
    {
        switch (c)
        {
        case 'o':
            outfilename = optarg;
            break;
        case 'c':
            out_c = true;
            break;
        case 'v':
            printf("version is 0.0.1\n");
            break;
        case 'h':
        case '?':
            display_usage();
            break;
        case 0:
            if (strcmp("ir", opts[index].name) == 0)
            {
                out_ir = true;
            }
            break;
        default:
            printf("------\n");
        }
    }
    int input_size = argc - optind;
    if (input_size < 1)
    {
        fprintf(stderr, "no input file");
        return 1;
    }
    const char **input_files = argv + optind;

    std::string input = input_files[0];
    printf("openning: %s \n", input.c_str());

    yyin = std::fopen(input.c_str(), "r");
    if (!yyin)
    {
        printf("open %s failed\n", input.c_str());
        return 1;
    }

    qb::parser parser;
    parser.parse();

    printf("parse done, no errors, generating llvm IR...\n");

    llvm::InitializeAllTargets();
    llvm::InitializeAllTargetMCs();
    llvm::InitializeAllAsmPrinters();
    llvm::InitializeAllAsmParsers();

    if (outfilename.empty())
    {
        outfilename = (fs::path(input).parent_path() / fs::path(input).stem()).string();
    }

    llvm::Module *module = new llvm::Module(fs::path(input).stem().string(), qbc::getGlobalContext());
    generateIR(program, module);


// ir to generate llvm IR
	if( out_ir ){
		module->print(llvm::outs(), nullptr);
		return 0;
	}

#if _WIN32
	std::string outobjname = outfilename + ".obj";
#else
	std::string outobjname = outfilename + ".o";
#endif
	std::error_code Err;

	std::shared_ptr<llvm::ToolOutputFile> Out(new llvm::ToolOutputFile(outobjname.c_str(), Err, llvm::sys::fs::F_RW));

	if(generateobj(Out, module)==0) 
		printf("======== object file writed to %s ===========\n", outobjname.c_str());

	if(out_c)
	{
		// compile to excuteable
        std::stringstream cmd;
#if _WIN32 && !defined(__MINGW32__)
        cmd << "link.exe /out:" << outfilename << ".exe " << outobjname << " msvcrt.lib";
#else
		// invoke  gcc
		// fs::path libdir = fs::path(argv[0]).parent_path().string();
		// fs::path libbrt_a = libdir / "libbrt.a";
		std::string linker_commandline = getenv("CC") ? getenv("CC") : "cc";
        cmd << linker_commandline << " -o " << outfilename << " " << outobjname ; //<< " " << libbrt_a;
#endif
		printf("run linker: %s\n", cmd.str().c_str());
		if(std::system(cmd.str().c_str())==0)
			Out->keep(); // keep the file if linker runs fine

	} else {
		// no delete obj file
		Out->keep();
	}
  
    return 0;
}
